const UnionFind = require('../dataStruct/unionFind').UnionFind;
const DinicAlog = require('../dataStruct/dinic').DinicAlog;
const Dump = require('../dataStruct/dump').Dump;
const Graph = require('../dataStruct/graph').Graph;

function speakArrayGraph() {
    let result = [];
    return (arr) => {
        for (let i = 0; i < arr.length; ++i) {
            result.push(`a_${i}=${arr[i] === undefined || arr[i] === null? '#': arr[i]}`);
        }
        return `$$\n\{${result.join(",")}\}\n$$`
    };
}

function speakMermaidGraph(mode = "TD") {
    let result = [`graph ${mode}`];
    return (edges) => {
        let nodes = new Set();
        for (let edge of edges) {
            if(edge.length <= 0) {
                throw new Error("edge is valid.");
            }

            if(edge.length == 1) {
                nodes.add(edge[0]);
            } else {
                nodes.add(edge[0]);
                nodes.add(edge[1]);
            }
        }

        for (let node of nodes) {
            result.push(`id${node}((${node}))`);
        }

        for (let edge of edges) {
            if(edge.length < 2) continue;
            let weightTxt = !isNaN(edge[2]) ? `|${edge[2]}|` : "";
            result.push(`id${edge[0]}-->${weightTxt}id${edge[1]}`);
        }

        return [`\`\`\`mermaid`, ...result, `\`\`\``, ""].join('\n');
    }
}

class ResponsibilityChain {
    constructor() {
        this.next = null;
    }

    setNextProcessor(processor) {
        if (processor instanceof ResponsibilityChain) {
            this.next = processor;
            return processor;
        }
        throw new Error(`processor ${processor} isn't a valid chain processor of responsibility.`);
    }

    process(obj) {
        if (this.canProcess instanceof Function &&
            this.canProcess(obj) &&
            this.customProcess instanceof Function) {
            return this.customProcess(obj);
        } else if (this.next != null) {
            return this.next.process(obj);
        }
        return null;
    }
}

class VoidDrawer extends ResponsibilityChain {
    canProcess() {
        return true;
    }

    customProcess() {
        return "";
    }
}

class UnionFindDrawer extends ResponsibilityChain {
    canProcess(obj) {
        return obj instanceof UnionFind;
    }

    customProcess(unionFind) {
        let parent = unionFind.parent,
            sz = unionFind.sz,
            edges = [];

        for (let i = 0; i < parent.length; ++i) {
            edges.push([i, parent[i], sz[i]]);
        }

        return speakMermaidGraph("LR")(edges);
    }
}

class DinicAlogDrawer extends ResponsibilityChain {
    canProcess(obj) {
        return obj instanceof DinicAlog || obj instanceof Graph;
    }

    customProcess(dinicGraph) {
        let head = dinicGraph.head,
            parent = dinicGraph.parent,
            V = dinicGraph.V,
            streams = dinicGraph.streams,
            w = dinicGraph.w,
            depths = dinicGraph.depths,
            edges = [];

        for (let i = 0; i < head.length; ++i) {
            let u = head[i];
            while (u >= 0) {
                let source = depths ? `${i}_${depths[i]}` : i,
                    target = depths ? `${V[u]}_${depths[V[u]]}` : V[u];
                if (w) {
                    w[u] > 0 ? (edges.push([source, target, w[u]])) : null;
                    w[u ^ 1] > 0 ? (edges.push([target, source, w[u ^ 1]])) : null;
                } else {
                    edges.push([source, target, streams[u]]);
                }
                u = parent[u];
            }
        }

        return speakMermaidGraph()(edges);
    }
}

class ArrayDrawer extends ResponsibilityChain {
    canProcess(obj) {
        return obj instanceof Array;
    }

    customProcess(arr) {
        return speakArrayGraph()(arr);
    }
}

class DumpDrawer extends ResponsibilityChain {
    canProcess(obj) {
        return obj instanceof Dump;
    }

    customProcess(dump) {
        let edges = [];

        if(dump.store.length == 1) {
            edges.push([`${0}_${dump.store[0]}`]);
        } else {
            for(let i=dump.store.length-1; i > 0; --i) {
                let parent = (i - 1) >> 1;
                edges.push([`${i}_${dump.store[i]}`, `${parent}_${dump.store[parent]}`]);
            }
        }

        return speakMermaidGraph()(edges);
    }
}

exports.UnionFindDrawer = UnionFindDrawer;
exports.DinicAlogDrawer = DinicAlogDrawer;
exports.ArrayDrawer = ArrayDrawer;
exports.VoidDrawer = VoidDrawer;
exports.DumpDrawer = DumpDrawer;